﻿namespace MicroCloud.Utils
{
    /// <summary>
    /// 参数检查辅助扩展方法类
    /// </summary>
    public static class CheckExtensions
    {
        private static readonly ConcurrentDictionary<Type, ConcurrentDictionary<PropertyInfo, ValidationAttribute[]>> ValidationDict = new();

        /// <summary>
        /// 验证指定值的断言 <paramref name="assertion"/> 是否为真，如果不为真，抛出指定消息 <paramref name="message"/> 的指定类型 <typeparamref name="TException"/> 异常
        /// </summary>
        /// <typeparam name="TException">异常类型</typeparam>
        /// <param name="assertion">要验证的断言。</param>
        /// <param name="message">异常消息。</param>
        private static void Require<TException>(bool assertion, string message) where TException : System.Exception
        {
            if (assertion) return;
            if (string.IsNullOrEmpty(message)) throw new ArgumentNullException(nameof(message));

            TException exception = (TException)Activator.CreateInstance(typeof(TException), message);
            throw exception;
        }

        /// <summary>
        /// 验证指定值的断言表达式是否为真，不为值抛出 <see cref="System.Exception"/> 异常
        /// </summary>
        /// <param name="value"></param>
        /// <param name="assertionFunc">要验证的断言表达式</param>
        /// <param name="message">异常消息</param>
        public static void Required<T>(this T value, Func<T, bool> assertionFunc, string message)
        {
            if (assertionFunc == null) throw new ArgumentNullException(nameof(assertionFunc));

            Require<System.Exception>(assertionFunc(value), message);
        }

        /// <summary>
        /// 验证指定值的断言表达式是否为真，不为真抛出 <typeparamref name="TException"/> 异常
        /// </summary>
        /// <typeparam name="T">要判断的值的类型</typeparam>
        /// <typeparam name="TException">抛出的异常类型</typeparam>
        /// <param name="value">要判断的值</param>
        /// <param name="assertionFunc">要验证的断言表达式</param>
        /// <param name="message">异常消息</param>
        public static void Required<T, TException>(this T value, Func<T, bool> assertionFunc, string message) where TException : System.Exception
        {
            if (assertionFunc == null) throw new ArgumentNullException(nameof(assertionFunc));

            Require<TException>(assertionFunc(value), message);
        }

        /// <summary>
        /// 检查参数不能为空引用，否则抛出 <see cref="ArgumentNullException"/> 异常
        /// </summary>
        /// <param name="value"></param>
        /// <param name="paramName">参数名称</param>
        /// <exception cref="ArgumentNullException"></exception>
        public static void CheckNotNull<T>(this T value, string paramName) where T : class
        {
            var message = I18N.T(Resources.ParameterCheck_NotNull, paramName);

            Require<ArgumentNullException>(value != null, message);
        }

        /// <summary>
        /// 检查字符串不能为空引用或空字符串，否则抛出 <see cref="ArgumentNullException"/> 异常或 <see cref="ArgumentException"/> 异常
        /// </summary>
        /// <param name="value"></param>
        /// <param name="paramName">参数名称。</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentException"></exception>
        public static void CheckNotNullOrEmpty(this string value, string paramName)
        {
            value.CheckNotNull(paramName);
            var message = I18N.T(Resources.ParameterCheck_NotNullOrEmpty_String, paramName);
            Require<ArgumentException>(!string.IsNullOrEmpty(value), message);
        }

        /// <summary>
        /// 检查Guid值不能为Guid.Empty，否则抛出 <see cref="ArgumentException"/> 异常
        /// </summary>
        /// <param name="value"></param>
        /// <param name="paramName">参数名称。</param>
        /// <exception cref="ArgumentException"></exception>
        public static void CheckNotEmpty(this Guid value, string paramName)
        {
            //Require<ArgumentException>(value != Guid.Empty, string.Format(MicroCloud.Properties.Resources.ParameterCheck_NotEmpty_Guid, paramName));
            var message = I18N.T(Resources.ParameterCheck_NotEmpty_Guid, paramName);
            Require<ArgumentException>(value != Guid.Empty, message);
        }

        /// <summary>
        /// 检查集合不能为空引用或空集合，否则抛出 <see cref="ArgumentNullException"/> 异常或 <see cref="ArgumentException"/> 异常
        /// </summary>
        /// <typeparam name="T">集合项的类型。</typeparam>
        /// <param name="collection"></param>
        /// <param name="paramName">参数名称。</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentException"></exception>
        public static void CheckNotNullOrEmpty<T>(this IEnumerable<T> collection, string paramName)
        {
            collection.CheckNotNull(paramName);
            var message = I18N.T(Resources.ParameterCheck_NotNullOrEmpty_Collection, paramName);
            Require<ArgumentException>(collection.Any(), message);
        }

        /// <summary>
        /// 检查参数必须小于[或可等于，参数canEqual]指定值，否则抛出 <see cref="ArgumentOutOfRangeException"/> 异常
        /// </summary>
        /// <typeparam name="T">参数类型。</typeparam>
        /// <param name="value"></param>
        /// <param name="paramName">参数名称。</param>
        /// <param name="target">要比较的值。</param>
        /// <param name="canEqual">是否可等于。</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public static void CheckLessThan<T>(this T value, string paramName, T target, bool canEqual = false) where T : IComparable<T>
        {
            bool flag = canEqual ? value.CompareTo(target) <= 0 : value.CompareTo(target) < 0;
            //string format = canEqual ? MicroCloud.Properties.Resources.ParameterCheck_NotLessThanOrEqual : MicroCloud.Properties.Resources.ParameterCheck_NotLessThan;
            string format = canEqual ? I18N.T(Resources.ParameterCheck_NotLessThanOrEqual) : I18N.T(Resources.ParameterCheck_NotLessThan);
            Require<ArgumentOutOfRangeException>(flag, string.Format(format, paramName, target));
        }

        /// <summary>
        /// 检查参数必须大于[或可等于，参数canEqual]指定值，否则抛出 <see cref="ArgumentOutOfRangeException"/> 异常
        /// </summary>
        /// <typeparam name="T">参数类型。</typeparam>
        /// <param name="value"></param>
        /// <param name="paramName">参数名称。</param>
        /// <param name="target">要比较的值。</param>
        /// <param name="canEqual">是否可等于。</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public static void CheckGreaterThan<T>(this T value, string paramName, T target, bool canEqual = false) where T : IComparable<T>
        {
            bool flag = canEqual ? value.CompareTo(target) >= 0 : value.CompareTo(target) > 0;
            //string format = canEqual ? MicroCloud.Properties.Resources.ParameterCheck_NotGreaterThanOrEqual : MicroCloud.Properties.Resources.ParameterCheck_NotGreaterThan;
            string format = canEqual ? I18N.T(Resources.ParameterCheck_NotGreaterThanOrEqual) : I18N.T(Resources.ParameterCheck_NotGreaterThan);
            Require<ArgumentOutOfRangeException>(flag, string.Format(format, paramName, target));
        }

        /// <summary>
        /// 检查参数必须在指定范围之间，否则抛出 <see cref="ArgumentOutOfRangeException"/> 异常
        /// </summary>
        /// <typeparam name="T">参数类型。</typeparam>
        /// <param name="value"></param>
        /// <param name="paramName">参数名称。</param>
        /// <param name="start">比较范围的起始值。</param>
        /// <param name="end">比较范围的结束值。</param>
        /// <param name="startEqual">是否可等于起始值</param>
        /// <param name="endEqual">是否可等于结束值</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public static void CheckBetween<T>(this T value, string paramName, T start, T end, bool startEqual = false, bool endEqual = false)
            where T : IComparable<T>
        {
            bool flag = startEqual ? value.CompareTo(start) >= 0 : value.CompareTo(start) > 0;
            string message = startEqual
                ? I18N.T(Resources.ParameterCheck_Between, paramName, start, end)
                : I18N.T(Resources.ParameterCheck_BetweenNotEqual, paramName, start, end, start);
            Require<ArgumentOutOfRangeException>(flag, message);
            
            flag = endEqual ? value.CompareTo(end) <= 0 : value.CompareTo(end) < 0;
            message = endEqual
                ? I18N.T(Resources.ParameterCheck_Between, paramName, start, end)
                : I18N.T(Resources.ParameterCheck_BetweenNotEqual, paramName, start, end, end);
            Require<ArgumentOutOfRangeException>(flag, message);
        }

        /// <summary>
        /// 检查指定路径的文件夹必须存在，否则抛出 <see cref="DirectoryNotFoundException"/> 异常
        /// </summary>
        /// <param name="directory"></param>
        /// <param name="paramName">参数名称。</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="DirectoryNotFoundException"></exception>
        public static void CheckDirectoryExists(this string directory, string paramName = null)
        {
            CheckNotNull(directory, paramName);
            var message = I18N.T(Resources.ParameterCheck_DirectoryNotExists, directory);
            Require<DirectoryNotFoundException>(Directory.Exists(directory), message);
        }

        /// <summary>
        /// 检查指定路径的文件必须存在，否则抛出 <see cref="FileNotFoundException"/> 异常
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="paramName">参数名称。</param>
        /// <exception cref="ArgumentNullException">当文件路径为null时</exception>
        /// <exception cref="FileNotFoundException">当文件路径不存在时</exception>
        public static void CheckFileExists(this string filename, string paramName = null)
        {
            CheckNotNull(filename, paramName);
            //Require<FileNotFoundException>(File.Exists(filename), string.Format(MicroCloud.Properties.Resources.ParameterCheck_FileNotExists, filename));
            var message = I18N.T(Resources.ParameterCheck_FileNotExists, filename);
            Require<FileNotFoundException>(File.Exists(filename), message);
        }       

        /// <summary>
        /// 验证对象的 <see cref="ValidationAttribute"/> 特性
        /// </summary>
        /// <param name="obj">要验证的源对象</param>
        public static void Validate(this object obj)
        {
            Check.NotNull(obj, nameof(obj));
            Type type = obj.GetType();
            if (!ValidationDict.TryGetValue(type, out ConcurrentDictionary<PropertyInfo, ValidationAttribute[]> dict))
            {
                PropertyInfo[] properties = type.GetProperties();
                dict = new ConcurrentDictionary<PropertyInfo, ValidationAttribute[]>();
                if (properties.Length == 0)
                {
                    ValidationDict[type] = dict;
                    return;
                }

                foreach (PropertyInfo property in properties)
                {
                    dict[property] = null;
                }

                ValidationDict[type] = dict;
            }

            foreach (PropertyInfo property in dict.Keys)
            {
                if (!dict.TryGetValue(property, out ValidationAttribute[] attributes) || attributes == null)
                {
                    attributes = property.GetAttributes<ValidationAttribute>();
                    dict[property] = attributes;
                }

                if (attributes.Length == 0)
                {
                    continue;
                }

                object value = property.GetValue(obj);
                foreach (ValidationAttribute attribute in attributes)
                {
                    attribute.Validate(value, property.Name);
                }
            }
        }

        /// <summary>
        /// InputDto属性验证
        /// </summary>
        public static void Validate<TKey>(this IEnumerable<IInputDto<TKey>> dtos) where TKey : IEquatable<TKey>
        {
            IInputDto<TKey>[] inputDtos = dtos as IInputDto<TKey>[] ?? dtos.ToArray();
            Check.NotNull(inputDtos, nameof(dtos));
            foreach (IInputDto<TKey> dto in inputDtos)
            {
                dto.Validate();
            }
        }

    }
}